/*
---
name: Swiff.Uploader

description: Swiff.Uploader - Flash FileReference Control

requires: [Core/Swiff, Core/Fx, Core/Class, Core/Class.Extras, Core/Browser, Core/Element]

provides: [Swiff.Uploader, Swiff.Uploader.File]

version: 3.0

license: MIT License

author: Harald Kirschner <http://digitarald.de>
author: Valerio Proietti, <http://mad4milk.net>
...
*/

Swiff.Uploader = new Class({

  Extends: Swiff,

  Implements: Events,

  options: {
    path: 'Swiff.Uploader.swf',

    target: null,
    zIndex: 9999,

    callBacks: null,
    params: {
      wMode: 'opaque',
      menu: 'false',
      allowScriptAccess: 'always'
    },

    typeFilter: null,
    multiple: true,
    queued: true,
    verbose: false,
    height: 30,
    width: 100,
    passStatus: null,

    url: null,
    method: null,
    data: null,
    mergeData: true,
    fieldName: null,

    fileSizeMin: 1,
    fileSizeMax: null, // Official limit is 100 MB for FileReference, but I tested up to 2Gb!
    allowDuplicates: false,
    timeLimit: (Browser.Platform.linux) ? 0 : 30,

    policyFile: null,
    buttonImage: null,

    fileListMax: 0,
    fileListSizeMax: 0,

    instantStart: false,
    appendCookieData: false,

    fileClass: null
    /*
    onLoad: function(){},
    onFail: function(){},
    onStart: function(){},
    onQueue: function(){},
    onComplete: function(){},
    onBrowse: function(){},
    onDisabledBrowse: function(){},
    onCancel: function(){},
    onSelect: function(){},
    onSelectSuccess: function(){},
    onSelectFail: function(){},

    onButtonEnter: function(){},
    onButtonLeave: function(){},
    onButtonDown: function(){},
    onButtonDisable: function(){},

    onFileStart: function(){},
    onFileStop: function(){},
    onFileRequeue: function(){},
    onFileOpen: function(){},
    onFileProgress: function(){},
    onFileComplete: function(){},
    onFileRemove: function(){},

    onBeforeStart: function(){},
    onBeforeStop: function(){},
    onBeforeRemove: function(){}
    */
  },

  initialize: function(options) {
    // protected events to control the class, added
    // before setting options (which adds own events)
    this.addEvent('load', this.initializeSwiff, true)
      .addEvent('select', this.processFiles, true)
      .addEvent('complete', this.update, true)
      .addEvent('fileRemove', function(file) {
        this.fileList.erase(file);
      }.bind(this), true);

    this.setOptions(options);

    // callbacks are no longer in the options, every callback
    // is fired as event, this is just compat
    if (this.options.callBacks) {
      Object.each(this.options.callBacks, function(fn, name) {
        this.addEvent(name, fn);
      }, this);
    }

    this.options.callBacks = {
      fireCallback: this.fireCallback.bind(this)
    };

    var path = this.options.path;
    if (!path.contains('?')) path += '?noCache=' + Date.now(); // cache in IE

    // container options for Swiff class
    this.options.container = this.box = new Element('span', {'class': 'swiff-uploader-box',events: { click: function(e) { e.stopPropagation(); } }}).inject(document.id(this.options.container) || document.body);

    // target
    this.target = document.id(this.options.target);
    if(this.target) {
      var scroll = window.getScroll();
      this.box.setStyles({
        position: 'absolute',
        visibility: 'visible',
        zIndex: this.options.zIndex,
        overflow: 'hidden',
        height: 1, width: 1,
        top: scroll.y, left: scroll.x
      });

      // we force wMode to transparent for the overlay effect
      this.parent(path, {
        params: {
          wMode: 'transparent'
        },
        height: '100%',
        width: '100%'
      });

      this.target.addEvent('mouseenter', this.reposition.bind(this));

      // button interactions, relayed to to the target
      this.addEvents({
        buttonEnter: this.targetRelay.pass('mouseenter',this),
        buttonLeave: this.targetRelay.pass('mouseleave',this),
        buttonDown: this.targetRelay.pass('mousedown',this),
        buttonDisable: this.targetRelay.pass('disable',this)
      });

      this.reposition();
      window.addEvent('resize', this.reposition.bind(this));
    } else {
      this.parent(path);
    }

    this.inject(this.box);

    this.fileList = [];

    this.size = this.uploading = this.bytesLoaded = this.percentLoaded = 0;

    if (Browser.Plugins.Flash.version < 9) {
      this.fireEvent('fail', ['flash']);
    } else {
      this.verifyLoad.delay(1000, this);
    }
  },

  verifyLoad: function() {
    if (this.loaded) return;
    if (!this.object.parentNode) {
      this.fireEvent('fail', ['disabled']);
    } else if (this.object.style.display == 'none') {
      this.fireEvent('fail', ['hidden']);
    } else if (!this.object.offsetWidth) {
      this.fireEvent('fail', ['empty']);
    }
  },

  fireCallback: function(name, args) {
    // file* callbacks are relayed to the specific file
    if (name.substr(0, 4) == 'file') {
      // updated queue data is the second argument
      if (args.length > 1) this.update(args[1]);
      var data = args[0];

      var file = this.findFile(data.id);
      this.fireEvent(name, file || data, 5);
      if (file) {
        var fire = name.replace(/^file([A-Z])/, function($0, $1) {
          return $1.toLowerCase();
        });
        file.update(data).fireEvent(fire, [data], 10);
      }
    } else {
      this.fireEvent(name, args, 5);
    }
  },

  update: function(data) {
    // the data is saved right to the instance
    Object.append(this, data);
    this.fireEvent('queue', [this], 10);
    return this;
  },

  findFile: function(id) {
    for (var i = 0; i < this.fileList.length; i++) {
      if (this.fileList[i].id == id) return this.fileList[i];
    }
    return null;
  },

  initializeSwiff: function() {
    this.appendCookieData();          // looks like there's a bit of trouble with xSetOptions, so we circumvent it by passing it all in one go through xInitialize
	// if (typeof console !== 'undefined' && console.log) console.log('initializeSwiff: data count = ' + this.options.data.length + ' : ' + this.options.data);

    // extracted options for the swf
    this.remote('xInitialize', {
      typeFilter: this.options.typeFilter,
      multiple: this.options.multiple,
      queued: this.options.queued,
      verbose: this.options.verbose,
      width: this.options.width,
      height: this.options.height,
      passStatus: this.options.passStatus,
      url: this.options.url,
      method: this.options.method,
      data: this.options.data,
      mergeData: this.options.mergeData,
      fieldName: this.options.fieldName,
      fileSizeMin: this.options.fileSizeMin,
      fileSizeMax: this.options.fileSizeMax,
      allowDuplicates: this.options.allowDuplicates,
      timeLimit: this.options.timeLimit,
      policyFile: this.options.policyFile,
      buttonImage: this.options.buttonImage
    });

    this.loaded = true;
  },

  targetRelay: function(name) {
    if (this.target) this.target.fireEvent(name);
  },

  reposition: function(coords) {
    // update coordinates, manual or automatically
    coords = coords || (this.target && this.target.offsetHeight) ? this.target.getCoordinates(this.box.getOffsetParent()) : {top: window.getScrollTop(), left: 0, width: 40, height: 40};
    this.box.setStyles(coords);
    this.fireEvent('reposition', [coords, this.box, this.target]);
  },

  setOptions: function(options) {
	// if (typeof console !== 'undefined' && console.log) console.log('Swiff.Uploader: BASE::setOptions');
    if (options) {
      if (options.url) options.url = Swiff.Uploader.qualifyPath(options.url);
      if (options.buttonImage) options.buttonImage = Swiff.Uploader.qualifyPath(options.buttonImage);
      this.parent(options);
      if (this.loaded) {
		this.remote('xSetOptions', options);
    }
    }
    return this;
  },

  setEnabled: function(status) {
    this.remote('xSetEnabled', status);
  },

  start: function() {
    this.fireEvent('beforeStart');
    this.remote('xStart');
  },

  stop: function() {
    this.fireEvent('beforeStop');
    this.remote('xStop');
  },

  remove: function() {
    this.fireEvent('beforeRemove');
    this.remote('xRemove');
  },

  fileStart: function(file) {
    this.remote('xFileStart', file.id);
  },

  fileStop: function(file) {
    this.remote('xFileStop', file.id);
  },

  fileRemove: function(file) {
    this.remote('xFileRemove', file.id);
  },

  fileRequeue: function(file) {
    this.remote('xFileRequeue', file.id);
  },

  appendCookieData: function() {
    var append = this.options.appendCookieData;
	// if (typeof console !== 'undefined' && console.log) console.log('appendCookieData: ' + (1 * append) + ' / ' + append);
    if (!append) return;

    var hash = {};
    //if (typeof console !== 'undefined' && console.log) console.log('appendCookieData: ENTIRE cookie: "' + document.cookie + '"');
    document.cookie.split(/;\s*/).each(function(cookie) {
      cookie = cookie.split('=');
      //if (typeof console !== 'undefined' && console.log) console.log('appendCookieData: cookie: "' + cookie[0] + '"(' + cookie.length + ') = "' + (cookie.length > 1 ? cookie[1] : '???') + '"');
      if (cookie.length == 2) {
        //hash['\"' + decodeURIComponent(cookie[0]) + '\"'] = decodeURIComponent(cookie[1]);    // allow session IDs such as the ASP.NET ones, which come with a dot, etc.
        hash[decodeURIComponent(cookie[0])] = decodeURIComponent(cookie[1]);
      }
    });

    var data = this.options.data || {};
    if (typeOf(append) == 'string') {
      data[append] = hash;
  } else {
    Object.append(data, hash);
  }
    this.setOptions({data: data});
  },

  processFiles: function(successraw, failraw, queue) {
    var cls = this.options.fileClass || Swiff.Uploader.File;

    var fail = [], success = [];

    if (successraw) {
      successraw.each(function(data) {
        var ret = new cls(this, data);
        if (!ret.validate()) {
          ret.remove.delay(10, ret);
          fail.push(ret);
        } else {
          this.size += data.size;
          this.fileList.push(ret);
          success.push(ret);
          ret.render();
        }
      }, this);

      this.fireEvent('selectSuccess', [success], 10);
    }

    if (failraw || fail.length) {
      fail.append((failraw) ? failraw.map(function(data) {
        return new cls(this, data);
      }, this) : []).each(function(file) {
        file.invalidate().render();
      });

      this.fireEvent('selectFail', [fail], 10);
    }

    this.update(queue);

    if (this.options.instantStart && success.length) this.start();
  }

});

Object.append(Swiff.Uploader, {

  STATUS_QUEUED: 0,
  STATUS_RUNNING: 1,
  STATUS_ERROR: 2,
  STATUS_COMPLETE: 3,
  STATUS_STOPPED: 4,

  log: function() {
    if (window.console && console.info) console.info.apply(console, arguments);
  },

  unitLabels: {
    b: [{min: 1, unit: 'B'}, {min: 1024, unit: 'kB'}, {min: 1048576, unit: 'MB'}, {min: 1073741824, unit: 'GB'}],
    s: [{min: 1, unit: 's'}, {min: 60, unit: 'm'}, {min: 3600, unit: 'h'}, {min: 86400, unit: 'd'}]
  },

  formatUnit: function(base, type, join) {
    var labels = Swiff.Uploader.unitLabels[(type == 'bps') ? 'b' : type];
    var append = (type == 'bps') ? '/s' : '';
    var i, l = labels.length, value;

    if (base < 1) return '0 ' + labels[0].unit + append;

    if (type == 's') {
      var units = [];

      for (i = l - 1; i >= 0; i--) {
        value = Math.floor(base / labels[i].min);
        if (value) {
          units.push(value + ' ' + labels[i].unit);
          base -= value * labels[i].min;
          if (!base) break;
        }
      }

      return (join === false) ? units : units.join(join || ', ');
    }

    for (i = l - 1; i >= 0; i--) {
      value = labels[i].min;
      if (base >= value) break;
    }

    return (base / value).toFixed(1) + ' ' + labels[i].unit + append;
  }

});

Swiff.Uploader.qualifyPath = (function() {

  var anchor;

  return function(path) {
    (anchor || (anchor = new Element('a'))).href = path;
    return anchor.href;
  };

})();

Swiff.Uploader.File = new Class({

  Implements: Events,

  initialize: function(base, data) {
    this.base = base;
    this.update(data);
  },

  update: function(data) {
    return Object.append(this, data);
  },

  validate: function() {
    var options = this.base.options;

    if (options.fileListMax && this.base.fileList.length >= options.fileListMax) {
      this.validationError = 'fileListMax';
      return false;
    }

    if (options.fileListSizeMax && (this.base.size + this.size) > options.fileListSizeMax) {
      this.validationError = 'fileListSizeMax';
      return false;
    }

    return true;
  },

  invalidate: function() {
    this.invalid = true;
    this.base.fireEvent('fileInvalid', this, 10);
    return this.fireEvent('invalid', this, 10);
  },

  render: function() {
    return this;
  },

  setOptions: function(options) {
	//if (typeof console !== 'undefined' && console.log) console.log('Swiff.Uploader: File::setOptions');
    if (options) {
      if (options.url) {
		options.url = Swiff.Uploader.qualifyPath(options.url);
    }
      this.base.remote('xFileSetOptions', this.id, options);
      this.options = Object.merge(this.base.options, options);
    }
    return this;
  },

  start: function() {
    this.base.fileStart(this);
    return this;
  },

  stop: function() {
    this.base.fileStop(this);
    return this;
  },

  remove: function() {
    this.base.fileRemove(this);
    return this;
  },

  requeue: function() {
    this.base.fileRequeue(this);
  }

});
